#!/usr/bin/env python3
# coding=utf-8
__version__ = "0.1"

import datetime
import sys
import unittest
from xml.sax import saxutils

python3 = (sys.version_info[0] > 2)
if python3:
    import io as StringIO
else:
    pass

class OutputRedirector(object):
    """ Wrapper to redirect stdout or stderr """
    def __init__(self, fp):
        self.fp = fp

    def write(self, s):
        self.fp.write(s)

    def writelines(self, lines):
        self.fp.writelines(lines)

    def flush(self):
        self.fp.flush()

stdout_redirector = OutputRedirector(sys.stdout)
stderr_redirector = OutputRedirector(sys.stderr)

# ----------------------------------------------------------------------
# Template Chinese

class Template_CN(object):
    STATUS = {
    0: u'通过',
    1: u'失败',
    2: u'错误',
    }

    # 增加 tester
    DEFAULT_TITLE = '测试报告'
    DEFAULT_DESCRIPTION = ''
    DEFAULT_TESTER = 'GuoXiaozhen'

    # ------------------------------------------------------------------------
    # HTML Template

    HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>%(title)s</title>
    <meta name="generator" content="%(generator)s"/>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    
    <link href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet">

    %(stylesheet)s
    <script src="https://cdn.bootcss.com/echarts/3.8.5/echarts.common.min.js"></script>

</head>
<body>
    <script language="javascript" type="text/javascript"><!--
    output_list = Array();
    
    /*level 增加测试结果的筛选条件  
    0:Summary //all hiddenRow
    1:Pass    //pt none, ft hiddenRow, et hiddenRow
    2:Failed  //pt hiddenRow, ft none, et hiddenRow
    3:Error    //pt hiddenRow, ft hiddenRow, et none
    4:All     //pt none, ft none, et none
    */
    
    /* level - 0:Summary; 1:Pass; 2:Failed; 3:Error; 4: All*/
    function showCase(level) {
        trs = document.getElementsByTagName("tr");
        for (var i = 0; i < trs.length; i++) {
            tr = trs[i];
            id = tr.id;
            if (id.substr(0,2) == 'ft') {
            if (level == 1 || level == 3 ||  level == 0) {
                tr.className = 'hiddenRow';
            }
            else {
                tr.className = '';
                }
            }
            if (id.substr(0,2) == 'pt') {
                if (level == 2 || level == 3 ||  level == 0){
                    tr.className = 'hiddenRow';
                }
                else {
                    tr.className = '';
                }
            }
            if (id.substr(0,2) == 'et') {
                if (level == 1 || level == 2 ||  level == 0 ){
                    tr.className = 'hiddenRow';
                }
                else {
                    tr.className = '';
                }
            }
        }
    }
    /* 优化详情与收缩不生效  */
    function showClassDetail(cid, count) {
        var id_list = Array(count);
        var toHide = 1;
        for (var i = 0; i < count; i++) {
            tid0 = 't' + cid.substr(1) + '_' + (i+1);
            tid = 'f' + tid0;
            tr = document.getElementById(tid);
            if (!tr) {
                tid = 'p' + tid0;
                tr = document.getElementById(tid);
            }
            /* 增加error */
            if (!tr) {
                tid = 'e' + tid0;
                tr = document.getElementById(tid);
            }
            id_list[i] = tid;
            if (tr.className) {
                toHide = 0;
            }
        }
        for (var i = 0; i < count; i++) {
            tid = id_list[i];
            if (toHide) {
                document.getElementById('div_'+tid).style.display = 'none'
                document.getElementById(tid).className = 'hiddenRow';
            }
            else {
                document.getElementById(tid).className = '';
            }
        }
    }


    function showTestDetail(div_id){
        var details_div = document.getElementById(div_id)
        var displayState = details_div.style.display
        // alert(displayState)
        if (displayState != 'block' ) {
            displayState = 'block'
            details_div.style.display = 'block'
        }
        else {
            details_div.style.display = 'none'
        }
    }
    
    
    function html_escape(s) {
        s = s.replace(/&/g,'&amp;');
        s = s.replace(/</g,'&lt;');
        s = s.replace(/>/g,'&gt;');
        return s;
    }
    /* 增加selenium结合unittest测试的结果截图   引用自GoverSky*/
    function show_img(obj) {
        var obj1 = obj.nextElementSibling
        obj1.style.display='block'
        var index = 0;//每张图片的下标，
        var len = obj1.getElementsByTagName('img').length;
        var imgyuan = obj1.getElementsByClassName('imgyuan')[0]
        //var start=setInterval(autoPlay,500);
        obj1.onmouseover=function(){//当鼠标光标停在图片上，则停止轮播
            clearInterval(start);
        }
        obj1.onmouseout=function(){//当鼠标光标停在图片上，则开始轮播
            start=setInterval(autoPlay,1000);
        }    
        for (var i = 0; i < len; i++) {
            var font = document.createElement('font')
            imgyuan.appendChild(font)
        }
        var lis = obj1.getElementsByTagName('font');//得到所有圆圈
        changeImg(0)
        var funny = function (i) {
            lis[i].onmouseover = function () {
                index=i
                changeImg(i)
            }
        }
        for (var i = 0; i < lis.length; i++) {
            funny(i);
    }
    
    function autoPlay(){
        if(index>len-1){
            index=0;
            clearInterval(start); //运行一轮后停止
        }
        changeImg(index++);
    }
    imgyuan.style.width= 25*len +"px";
    //对应圆圈和图片同步
    function changeImg(index) {
        var list = obj1.getElementsByTagName('img');
        var list1 = obj1.getElementsByTagName('font');
        for (i = 0; i < list.length; i++) {
            list[i].style.display = 'none';
            list1[i].style.backgroundColor = 'white';
        }
        list[index].style.display = 'block';
        list1[index].style.backgroundColor = 'blue';
    }

    }
    function hide_img(obj){
        obj.parentElement.style.display = "none";
        obj.parentElement.getElementsByClassName('imgyuan')[0].innerHTML = "";
    }
    --></script>
    <div id="div_base">
        %(heading)s
        %(report)s
        %(ending)s
        %(chart_script)s
    </div>
</body>
</html>
"""
    # variables: (title, generator, stylesheet, heading, report, ending, chart_script)
    # 增加饼图
    ECHARTS_SCRIPT = """
        <script type="text/javascript">
            // 基于准备好的dom，初始化echarts实例
            var myChart = echarts.init(document.getElementById('chart'));

            // 指定图表的配置项和数据
            var option = {
                title : {
                    text: '测试执行情况',
                    x:'center'
                },
                tooltip : {
                    trigger: 'item',
                    formatter: "{a} <br/>{b} : {c} ({d}%%)"
                },
                color: ['#00A600', '#FF5151', '#FF8A19'],
                legend: {
                    orient: 'vertical',
                    left: 'left',
                    data: ['通过','失败','错误']
                },
                series : [
                    {
                        name: '测试执行情况',
                        type: 'pie',
                        radius : '60%%',
                        center: ['50%%', '60%%'],
                        data:[
                            {value:%(Pass)s, name:'通过'},
                            {value:%(fail)s, name:'失败'},
                            {value:%(error)s, name:'错误'}
                        ],
                        itemStyle: {
                            emphasis: {
                                shadowBlur: 10,
                                shadowOffsetX: 0,
                                shadowColor: 'rgba(0, 0, 0, 0.5)'
                            }
                        }
                    }
                ]
            };

            // 使用刚指定的配置项和数据显示图表。
            myChart.setOption(option);
        </script>
        """  # variables: (Pass, fail, error)
    # ------------------------------------------------------------------------
    # Stylesheet
    #
    # alternatively use a <link> for external style sheet, e.g.
    #   <link rel="stylesheet" href="$url" type="text/css">

    STYLESHEET_TMPL = """
<style type="text/css" media="screen">
body        { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; }
table       { font-size: 100%; }
pre         { white-space: pre-wrap;word-wrap: break-word; }

/* -- heading ---------------------------------------------------------------------- */
h1 {
	font-size: 16pt;
	color: gray;
}
.heading {
    margin-top: 0ex;
    margin-bottom: 1ex;
}

.heading .attribute {
    margin-top: 1ex;
    margin-bottom: 0;
}

.heading .description {
    margin-top: 2ex;
    margin-bottom: 3ex;
}

/* -- css div popup ------------------------------------------------------------------------ */
a.popup_link {
}

a.popup_link:hover {
    color: red;
}

.popup_window {
    display: none;
    position: relative;
    left: 0px;
    top: 0px;
    /*border: solid #627173 1px; */
    padding: 10px;
    background-color: #E6E6D6;
    font-family: "Lucida Console", "Courier New", Courier, monospace;
    text-align: left;
    font-size: 8pt;
    /* width: 500px;*/
}
.img{
	height: 100%;
	border-collapse: collapse;
    border: 2px solid #777;
}

.screenshots {
    z-index: 100;
	position:fixed;
	height: 80%;
    left: 50%;
    top: 50%;
    transform: translate(-50%,-50%);
	display: none;
}

.imgyuan{
    height: 20px;
    border-radius: 12px;
    background-color: red;
    padding-left: 13px;
    margin: 0 auto;
    position: relative;
    top: -40px;
    background-color: rgba(1, 150, 0, 0.3);
}
.imgyuan font{
    border:1px solid white;
    width:11px; 
    height:11px;
    border-radius:50%;
    margin-right: 9px;
    margin-top: 4px;
    display: block;
    float: left;
    background-color: white;
}
.close_shots {
    background-image: url();
    background-size: 22px 22px;
    -moz-background-size: 22px 22px;
    background-repeat: no-repeat;
    position: absolute;
    top: 5px;
    right: 5px;
    height: 22px;
    z-index: 99;
    width: 22px;
}

}
/* -- report ------------------------------------------------------------------------ */
#show_detail_line {
    margin-top: 3ex;
    margin-bottom: 1ex;
}
#result_table {
    width: 99%;
}
#header_row {
    font-weight: bold;
    color: white;
    background-color: #777;
}
#total_row  { font-weight: bold; }
.passClass  { background-color: #00DB00; }
.failClass  { background-color: #FF5151; }
.errorClass { background-color: #FF8A19; }
.passCase   { color: #00A600; }
.failCase   { color: #FF5151; font-weight: bold; }
.errorCase  { color: #FF8A19; font-weight: bold; }
.hiddenRow  { display: none; }
.testcase   { margin-left: 2em; }


/* -- ending ---------------------------------------------------------------------- */
#ending {
}

#div_base {
            position:absolute;
            top:0%;
            left:5%;
            right:5%;
            width: auto;
            height: auto;
            margin: -15px 0 0 0;
}

</style>
"""


    # ------------------------------------------------------------------------
    # Heading
    #
    # 修改饼图位置
    HEADING_TMPL = """
    <div class='page-header'>
        <h1>%(title)s
        </h1>
        <div style="float:left;width:50%%;">
            <p class='parameters'> %(parameters)s</p>
            <br>
            <br>
            <p class='description'> %(description)s</p>
        </div>
        <div id="chart" style="float:left;width:50%%;height:250px;"></di>
    </div>

""" # variables: (title, parameters, description)

    HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>
""" # variables: (name, value)


    # ------------------------------------------------------------------------
    # Report
    #

    REPORT_TMPL = u"""
<div class="btn-group btn-group-sm">
<button class="btn btn-primary" onclick='javascript:showCase(0)'>概览</button>
<button class="btn btn-success" onclick='javascript:showCase(1)'>成功</button>
<button class="btn btn-danger" onclick='javascript:showCase(2)'>失败</button>
<button class="btn btn-warning" onclick='javascript:showCase(3)'>错误</button>
<button class="btn btn-info" onclick='javascript:showCase(4)'>全部</button>
</div>
<p></p>
<table id='result_table' class="table table-bordered">
<colgroup>
<col align='left' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
</colgroup>
<tr id='header_row'>
    <td>测试套件/测试用例</td>
    <td>总数</td>
    <td>通过</td>
    <td>失败</td>
    <td>错误</td>
    <td>查看</td>
</tr>
%(test_list)s
<tr id='total_row'>
    <td>总计</td>
    <td>%(count)s</td>
    <td>%(Pass)s</td>
    <td>%(fail)s</td>
    <td>%(error)s</td>
    <td>通过率：%(passrate)s</td>
    
</tr>
</table>
""" # variables: (test_list, count, Pass, fail, error)

    REPORT_TMPL_IMAGES = u"""
<div class="btn-group btn-group-sm">
<button class="btn btn-primary" onclick='javascript:showCase(0)'>概览</button>
<button class="btn btn-success" onclick='javascript:showCase(1)'>成功</button>
<button class="btn btn-danger" onclick='javascript:showCase(2)'>失败</button>
<button class="btn btn-warning" onclick='javascript:showCase(3)'>错误</button>
<button class="btn btn-info" onclick='javascript:showCase(4)'>全部</button>
</div>
<p></p>
<table id='result_table' class="table table-bordered">
<colgroup>
<col align='left' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
</colgroup>
<tr id='header_row'>
    <td>测试套件/测试用例</td>
    <td>总数</td>
    <td>通过</td>
    <td>失败</td>
    <td>错误</td>
    <td>查看</td>
    <td>截图</td>
</tr>
%(test_list)s
<tr id='total_row'>
    <td>总计</td>
    <td>%(count)s</td>
    <td>%(Pass)s</td>
    <td>%(fail)s</td>
    <td>%(error)s</td>
    <td>通过率：%(passrate)s</td>
    <td>&nbsp;</td>
    
</tr>
</table>
""" # variables: (test_list, count, Pass, fail, error)

    REPORT_CLASS_TMPL = u"""
<tr class='%(style)s'>
    <td>%(desc)s</td>
    <td>%(count)s</td>
    <td>%(Pass)s</td>
    <td>%(fail)s</td>
    <td>%(error)s</td>
    <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">详情</a></td>
</tr>
""" # variables: (style, desc, count, Pass, fail, error, cid)

    REPORT_CLASS_TMPL_IMAGES = u"""
<tr class='%(style)s'>
    <td>%(desc)s</td>
    <td>%(count)s</td>
    <td>%(Pass)s</td>
    <td>%(fail)s</td>
    <td>%(error)s</td>
    <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">详情</a></td>
    <td>&nbsp;</td>
</tr>
""" # variables: (style, desc, count, Pass, fail, error, cid)

    # 修改详情展开与收缩
    REPORT_TEST_WITH_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'>
    <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
    <td colspan='5' align='center'>

    <!--css div popup start-->
    <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >
        %(status)s</a>

    <div id='div_%(tid)s' class="popup_window">
        <div style='text-align: right; color:red;cursor:pointer'>
        <a onfocus='this.blur();' onclick="document.getElementById('div_%(tid)s').style.display = 'none' " >
           [x]</a>
        </div>
        <pre>
        %(script)s
        </pre>
    </div>
    <!--css div popup end-->

    </td>
</tr>
""" # variables: (tid, Class, style, desc, status)

    REPORT_TEST_WITH_OUTPUT_TMPL_IMAGES = r"""
<tr id='%(tid)s' class='%(Class)s'>
    <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
    <td colspan='5' align='center'>

    <!--css div popup start-->
    <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >
        %(status)s</a>

    <div id='div_%(tid)s' class="popup_window">
        <div style='text-align: right; color:red;cursor:pointer'>
        <a onfocus='this.blur();' onclick="document.getElementById('div_%(tid)s').style.display = 'none' " >
           [x]</a>
        </div>
        <pre>
        %(script)s
        </pre>
    </div>
    <!--css div popup end-->

    </td>
    <td>%(img)s</td>
</tr>
""" # variables: (tid, Class, style, desc, status)

    REPORT_TEST_NO_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'>
    <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
    <td colspan='5' align='center'>%(status)s</td>
</tr>
""" # variables: (tid, Class, style, desc, status)

    REPORT_TEST_NO_OUTPUT_TMPL_IMAGES = r"""
<tr id='%(tid)s' class='%(Class)s'>
    <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
    <td colspan='5' align='center'>%(status)s</td>
    <td>%(img)s</td>
</tr>
""" # variables: (tid, Class, style, desc, status)


    REPORT_TEST_OUTPUT_TMPL = r"""
%(id)s: %(output)s
""" # variables: (id, output)

    IMG_TMPL = r"""
        <a  onfocus='this.blur();' href="javacript:void(0);" onclick="show_img(this)">显示截图</a>
    <div align="center" class="screenshots"  style="display:none">
        <a class="close_shots"  onclick="hide_img(this)"></a>
        %(imgs)s
        <div class="imgyuan"></div>
    </div>
    """

    # ------------------------------------------------------------------------
    # ENDING
    #

    # 版权和返回顶部
    ENDING_TMPL = r"""
<div id='ending'>
    <p align="center">
    Copyright &copy 2016
    <a href="http://blog.csdn.net/z_johnny" target="_blank"> HONGWEI </a>
    Inc. All rights reserved.
    </p>
</div>
 <div style=" position:fixed;right:50px; bottom:30px; width:20px; height:20px;cursor:pointer">
    <a href="#"><span class="glyphicon glyphicon-chevron-up" style = "font-size:30px;" aria-hidden="true">
    </span></a></div>
"""

# ----------------------------------------------------------------------
# Template English
class Template_EN(object):
    STATUS = {
        0: u'pass',
        1: u'fail',
        2: u'error',
    }

    # add tester
    DEFAULT_TITLE = 'Test Report'
    DEFAULT_DESCRIPTION = ''
    DEFAULT_TESTER = 'GuoXiaozhen'

    # ------------------------------------------------------------------------
    # HTML Template

    HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>%(title)s</title>
    <meta name="generator" content="%(generator)s"/>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    
    <link href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet">

    %(stylesheet)s
    <script src="https://cdn.bootcss.com/echarts/3.8.5/echarts.common.min.js"></script>

</head>
<body>
    <script language="javascript" type="text/javascript"><!--
    output_list = Array();
    
    /*level add test choice  
    0:Summary //all hiddenRow
    1:Pass    //pt none, ft hiddenRow, et hiddenRow
    2:Failed  //pt hiddenRow, ft none, et hiddenRow
    3:Error    //pt hiddenRow, ft hiddenRow, et none
    4:All     //pt none, ft none, et none
    */
    
    /* level - 0:Summary; 1:Pass; 2:Failed; 3:Error; 4: All*/
    function showCase(level) {
        trs = document.getElementsByTagName("tr");
        for (var i = 0; i < trs.length; i++) {
            tr = trs[i];
            id = tr.id;
            if (id.substr(0,2) == 'ft') {
            if (level == 1 || level == 3 ||  level == 0) {
                tr.className = 'hiddenRow';
            }
            else {
                tr.className = '';
                }
            }
            if (id.substr(0,2) == 'pt') {
                if (level == 2 || level == 3 ||  level == 0){
                    tr.className = 'hiddenRow';
                }
                else {
                    tr.className = '';
                }
            }
            if (id.substr(0,2) == 'et') {
                if (level == 1 || level == 2 ||  level == 0 ){
                    tr.className = 'hiddenRow';
                }
                else {
                    tr.className = '';
                }
            }
        }
    }
    /* Optimizing Details and Contraction Not Effective  */
    function showClassDetail(cid, count) {
        var id_list = Array(count);
        var toHide = 1;
        for (var i = 0; i < count; i++) {
            tid0 = 't' + cid.substr(1) + '_' + (i+1);
            tid = 'f' + tid0;
            tr = document.getElementById(tid);
            if (!tr) {
                tid = 'p' + tid0;
                tr = document.getElementById(tid);
            }
            /* 增加error */
            if (!tr) {
                tid = 'e' + tid0;
                tr = document.getElementById(tid);
            }
            id_list[i] = tid;
            if (tr.className) {
                toHide = 0;
            }
        }
        for (var i = 0; i < count; i++) {
            tid = id_list[i];
            if (toHide) {
                document.getElementById('div_'+tid).style.display = 'none'
                document.getElementById(tid).className = 'hiddenRow';
            }
            else {
                document.getElementById(tid).className = '';
            }
        }
    }


    function showTestDetail(div_id){
        var details_div = document.getElementById(div_id)
        var displayState = details_div.style.display
        // alert(displayState)
        if (displayState != 'block' ) {
            displayState = 'block'
            details_div.style.display = 'block'
        }
        else {
            details_div.style.display = 'none'
        }
    }
    
    
    function html_escape(s) {
        s = s.replace(/&/g,'&amp;');
        s = s.replace(/</g,'&lt;');
        s = s.replace(/>/g,'&gt;');
        return s;
    }
    /* add test screenshot   from GoverSky*/
    function show_img(obj) {
        var obj1 = obj.nextElementSibling
        obj1.style.display='block'
        var index = 0;
        var len = obj1.getElementsByTagName('img').length;
        var imgyuan = obj1.getElementsByClassName('imgyuan')[0]
        //var start=setInterval(autoPlay,500);
        obj1.onmouseover=function(){
            clearInterval(start);
        }
        obj1.onmouseout=function(){
            start=setInterval(autoPlay,1000);
        }    
        for (var i = 0; i < len; i++) {
            var font = document.createElement('font')
            imgyuan.appendChild(font)
        }
        var lis = obj1.getElementsByTagName('font');
        changeImg(0)
        var funny = function (i) {
            lis[i].onmouseover = function () {
                index=i
                changeImg(i)
            }
        }
        for (var i = 0; i < lis.length; i++) {
            funny(i);
    }
    
    function autoPlay(){
        if(index>len-1){
            index=0;
            clearInterval(start); 
        }
        changeImg(index++);
    }
    imgyuan.style.width= 25*len +"px";
    
    function changeImg(index) {
        var list = obj1.getElementsByTagName('img');
        var list1 = obj1.getElementsByTagName('font');
        for (i = 0; i < list.length; i++) {
            list[i].style.display = 'none';
            list1[i].style.backgroundColor = 'white';
        }
        list[index].style.display = 'block';
        list1[index].style.backgroundColor = 'blue';
    }

    }
    function hide_img(obj){
        obj.parentElement.style.display = "none";
        obj.parentElement.getElementsByClassName('imgyuan')[0].innerHTML = "";
    }
    --></script>
    <div id="div_base">
        %(heading)s
        %(report)s
        %(ending)s
        %(chart_script)s
    </div>
</body>
</html>
"""

    # variables: (title, generator, stylesheet, heading, report, ending, chart_script)
    # add Pie chart   from huilansame
    ECHARTS_SCRIPT = """
            <script type="text/javascript">
                var myChart = echarts.init(document.getElementById('chart'));
    
                var option = {
                    title : {
                        text: 'Test Result',
                        x:'center'
                    },
                    tooltip : {
                        trigger: 'item',
                        formatter: "{a} <br/>{b} : {c} ({d}%%)"
                    },
                    color: ['#00A600', '#FF5151', '#FF8A19'],
                    legend: {
                        orient: 'vertical',
                        left: 'left',
                        data: ['pass','fail','error']
                    },
                    series : [
                        {
                            name: 'Test Result',
                            type: 'pie',
                            radius : '60%%',
                            center: ['50%%', '60%%'],
                            data:[
                                {value:%(Pass)s, name:'pass'},
                                {value:%(fail)s, name:'fail'},
                                {value:%(error)s, name:'error'}
                            ],
                            itemStyle: {
                                emphasis: {
                                    shadowBlur: 10,
                                    shadowOffsetX: 0,
                                    shadowColor: 'rgba(0, 0, 0, 0.5)'
                                }
                            }
                        }
                    ]
                };
    
                myChart.setOption(option);
            </script>
            """  # variables: (Pass, fail, error)
    # ------------------------------------------------------------------------
    # Stylesheet
    #
    # alternatively use a <link> for external style sheet, e.g.
    #   <link rel="stylesheet" href="$url" type="text/css">


    STYLESHEET_TMPL = """
<style type="text/css" media="screen">
body        { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; }
table       { font-size: 100%; }
pre         { white-space: pre-wrap;word-wrap: break-word; }

/* -- heading ---------------------------------------------------------------------- */
h1 {
	font-size: 16pt;
	color: gray;
}
.heading {
    margin-top: 0ex;
    margin-bottom: 1ex;
}

.heading .attribute {
    margin-top: 1ex;
    margin-bottom: 0;
}

.heading .description {
    margin-top: 2ex;
    margin-bottom: 3ex;
}

/* -- css div popup ------------------------------------------------------------------------ */
a.popup_link {
}

a.popup_link:hover {
    color: red;
}

.popup_window {
    display: none;
    position: relative;
    left: 0px;
    top: 0px;
    /*border: solid #627173 1px; */
    padding: 10px;
    background-color: #E6E6D6;
    font-family: "Lucida Console", "Courier New", Courier, monospace;
    text-align: left;
    font-size: 8pt;
    /* width: 500px;*/
}
.img{
	height: 100%;
	border-collapse: collapse;
    border: 2px solid #777;
}

.screenshots {
    z-index: 100;
	position:fixed;
	height: 80%;
    left: 50%;
    top: 50%;
    transform: translate(-50%,-50%);
	display: none;
}

.imgyuan{
    height: 20px;
    border-radius: 12px;
    background-color: red;
    padding-left: 13px;
    margin: 0 auto;
    position: relative;
    top: -40px;
    background-color: rgba(1, 150, 0, 0.3);
}
.imgyuan font{
    border:1px solid white;
    width:11px; 
    height:11px;
    border-radius:50%;
    margin-right: 9px;
    margin-top: 4px;
    display: block;
    float: left;
    background-color: white;
}
.close_shots {
    background-image: url();
    background-size: 22px 22px;
    -moz-background-size: 22px 22px;
    background-repeat: no-repeat;
    position: absolute;
    top: 5px;
    right: 5px;
    height: 22px;
    z-index: 99;
    width: 22px;
}

}
/* -- report ------------------------------------------------------------------------ */
#show_detail_line {
    margin-top: 3ex;
    margin-bottom: 1ex;
}
#result_table {
    width: 99%;
}
#header_row {
    font-weight: bold;
    color: white;
    background-color: #777;
}
#total_row  { font-weight: bold; }
.passClass  { background-color: #00DB00; }
.failClass  { background-color: #FF5151; }
.errorClass { background-color: #FF8A19; }
.passCase   { color: #00A600; }
.failCase   { color: #FF5151; font-weight: bold; }
.errorCase  { color: #FF8A19; font-weight: bold; }
.hiddenRow  { display: none; }
.testcase   { margin-left: 2em; }


/* -- ending ---------------------------------------------------------------------- */
#ending {
}

#div_base {
            position:absolute;
            top:0%;
            left:5%;
            right:5%;
            width: auto;
            height: auto;
            margin: -15px 0 0 0;
}

</style>
"""


    # ------------------------------------------------------------------------
    # Heading
    #
    # change Pie chart local
    HEADING_TMPL = """
    <div class='page-header'>
        <h1>%(title)s
        </h1>
        <div style="float:left;width:50%%;">
            <p class='parameters'> %(parameters)s</p>
            <br>
            <br>
            <p class='description'> %(description)s</p>
        </div>
        <div id="chart" style="float:left;width:50%%;height:250px;"></di>
    </div>

""" # variables: (title, parameters, description)

    HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>
""" # variables: (name, value)


    # ------------------------------------------------------------------------
    # Report
    #

    REPORT_TMPL = u"""
<div class="btn-group btn-group-sm">
<button class="btn btn-primary" onclick='javascript:showCase(0)'>Overview</button>
<button class="btn btn-success" onclick='javascript:showCase(1)'>Passed</button>
<button class="btn btn-danger" onclick='javascript:showCase(2)'>Failed</button>
<button class="btn btn-warning" onclick='javascript:showCase(3)'>Error</button>
<button class="btn btn-info" onclick='javascript:showCase(4)'>ALL</button>
</div>
<p></p>
<table id='result_table' class="table table-bordered">
<colgroup>
<col align='left' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
</colgroup>
<tr id='header_row'>
    <td>Test Group/Test case</td>
    <td>Count</td>
    <td>Pass</td>
    <td>Fail</td>
    <td>Error</td>
    <td>View</td>
</tr>
%(test_list)s
<tr id='total_row'>
    <td>Total</td>
    <td>%(count)s</td>
    <td>%(Pass)s</td>
    <td>%(fail)s</td>
    <td>%(error)s</td>
    <td>Passing rate：%(passrate)s</td>

</tr>
</table>
""" # variables: (test_list, count, Pass, fail, error)

    REPORT_TMPL_IMAGES = u"""
<div class="btn-group btn-group-sm">
<button class="btn btn-primary" onclick='javascript:showCase(0)'>Overview</button>
<button class="btn btn-success" onclick='javascript:showCase(1)'>Passed</button>
<button class="btn btn-danger" onclick='javascript:showCase(2)'>Failed</button>
<button class="btn btn-warning" onclick='javascript:showCase(3)'>Error</button>
<button class="btn btn-info" onclick='javascript:showCase(4)'>ALL</button>
</div>
<p></p>
<table id='result_table' class="table table-bordered">
<colgroup>
<col align='left' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
</colgroup>
<tr id='header_row'>
<td>Test Group/Test case</td>
<td>Count</td>
<td>Pass</td>
<td>Fail</td>
<td>Error</td>
<td>View</td>
<td>Screenshot</td>
</tr>
%(test_list)s
<tr id='total_row'>
    <td>Total</td>
    <td>%(count)s</td>
    <td>%(Pass)s</td>
    <td>%(fail)s</td>
    <td>%(error)s</td>
    <td>Passing rate：%(passrate)s</td>
    <td>&nbsp;</td>
</tr>
</table>
""" # variables: (test_list, count, Pass, fail, error)

    REPORT_CLASS_TMPL = u"""
<tr class='%(style)s'>
    <td>%(desc)s</td>
    <td>%(count)s</td>
    <td>%(Pass)s</td>
    <td>%(fail)s</td>
    <td>%(error)s</td>
    <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Details</a></td>
</tr>
""" # variables: (style, desc, count, Pass, fail, error, cid)

    REPORT_CLASS_TMPL_IMAGES = u"""
<tr class='%(style)s'>
    <td>%(desc)s</td>
    <td>%(count)s</td>
    <td>%(Pass)s</td>
    <td>%(fail)s</td>
    <td>%(error)s</td>
    <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Details</a></td>
    <td>&nbsp;</td>
</tr>
""" # variables: (style, desc, count, Pass, fail, error, cid)

    # Modification Details Expansion and Contraction
    REPORT_TEST_WITH_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'>
    <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
    <td colspan='5' align='center'>

    <!--css div popup start-->
    <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >
        %(status)s</a>

    <div id='div_%(tid)s' class="popup_window">
        <div style='text-align: right; color:red;cursor:pointer'>
        <a onfocus='this.blur();' onclick="document.getElementById('div_%(tid)s').style.display = 'none' " >
           [x]</a>
        </div>
        <pre>
        %(script)s
        </pre>
    </div>
    <!--css div popup end-->

    </td>
</tr>
""" # variables: (tid, Class, style, desc, status)

    REPORT_TEST_WITH_OUTPUT_TMPL_IMAGES = r"""
<tr id='%(tid)s' class='%(Class)s'>
    <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
    <td colspan='5' align='center'>

    <!--css div popup start-->
    <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >
        %(status)s</a>

    <div id='div_%(tid)s' class="popup_window">
        <div style='text-align: right; color:red;cursor:pointer'>
        <a onfocus='this.blur();' onclick="document.getElementById('div_%(tid)s').style.display = 'none' " >
           [x]</a>
        </div>
        <pre>
        %(script)s
        </pre>
    </div>
    <!--css div popup end-->

    </td>
    <td>%(img)s</td>
</tr>
""" # variables: (tid, Class, style, desc, status)

    REPORT_TEST_NO_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'>
    <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
    <td colspan='5' align='center'>%(status)s</td>
</tr>
""" # variables: (tid, Class, style, desc, status)

    REPORT_TEST_NO_OUTPUT_TMPL_IMAGES = r"""
<tr id='%(tid)s' class='%(Class)s'>
    <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
    <td colspan='5' align='center'>%(status)s</td>
    <td>%(img)s</td>
</tr>
""" # variables: (tid, Class, style, desc, status)

    REPORT_TEST_OUTPUT_TMPL = r"""
%(id)s: %(output)s
""" # variables: (id, output)

    IMG_TMPL = r"""
        <a  onfocus='this.blur();' href="javacript:void(0);" onclick="show_img(this)">Show</a>
    <div align="center" class="screenshots"  style="display:none">
        <a class="close_shots"  onclick="hide_img(this)"></a>
        %(imgs)s
        <div class="imgyuan"></div>
    </div>
    """

    # ------------------------------------------------------------------------
    # ENDING
    #

    # 版权和返回顶部
    ENDING_TMPL = r"""
<div id='ending'>
    <p align="center">
    Copyright &copy 2016
    <a href="http://blog.csdn.net/z_johnny" target="_blank"> HONGWEI </a>
    Inc. All rights reserved.
    </p>
</div>
 <div style=" position:fixed;right:50px; bottom:30px; width:20px; height:20px;cursor:pointer">
    <a href="#"><span class="glyphicon glyphicon-chevron-up" style = "font-size:30px;" aria-hidden="true">
    </span></a></div>
"""
# -------------------- The end of the Template class -------------------


TestResult = unittest.TestResult

class _TestResult(TestResult):
    # note: _TestResult is a pure representation of results.
    # It lacks the output and reporting ability compares to unittest._TextTestResult.
    # 增加subtest和通过率
    def __init__(self, verbosity=1):
        TestResult.__init__(self)
        self.stdout0 = None
        self.stderr0 = None
        self.success_count = 0
        self.failure_count = 0
        self.error_count = 0
        self.verbosity = verbosity

        # result is a list of result in 4 tuple
        # (
        #   result code (0: success; 1: fail; 2: error),
        #   TestCase object,
        #   Test output (byte string),
        #   stack trace,
        # )
        self.result = []
        self.subtestlist = []
        self.passrate = float(0)



    def startTest(self, test):
        '''增加截图'''
        test.imgs = getattr(test, "imgs", [])
        TestResult.startTest(self, test)
        # just one buffer for both stdout and stderr
        self.outputBuffer = StringIO.StringIO()
        stdout_redirector.fp = self.outputBuffer
        stderr_redirector.fp = self.outputBuffer
        self.stdout0 = sys.stdout
        self.stderr0 = sys.stderr
        sys.stdout = stdout_redirector
        sys.stderr = stderr_redirector


    def complete_output(self):
        """
        Disconnect output redirection and return buffer.
        Safe to call multiple times.
        """
        if self.stdout0:
            sys.stdout = self.stdout0
            sys.stderr = self.stderr0
            self.stdout0 = None
            self.stderr0 = None
        return self.outputBuffer.getvalue()


    def stopTest(self, test):
        # Usually one of addSuccess, addError or addFailure would have been called.
        # But there are some path in unittest that would bypass this.
        # We must disconnect stdout in stopTest(), which is guaranteed to be called.
        self.complete_output()


    def addSuccess(self, test):
        if test not in self.subtestlist:
            self.success_count += 1
            TestResult.addSuccess(self, test)
            output = self.complete_output()
            self.result.append((0, test, output, ''))
            if self.verbosity > 1:
                sys.stderr.write(u'ok ')
                sys.stderr.write(str(test))
                sys.stderr.write(u'\n')
            else:
                sys.stderr.write(u'../..')

    def addError(self, test, err):
        '''增加截图'''
        self.error_count += 1
        TestResult.addError(self, test, err)
        _, _exc_str = self.errors[-1]
        output = self.complete_output()
        self.result.append((2, test, output, _exc_str))

        if python3:
            if not getattr(test, "driver",""):
                pass
            else:
                try:
                    driver = getattr(test, "driver")
                    test.imgs.append(driver.get_screenshot_as_base64())
                except Exception:
                    pass

        if self.verbosity > 1:
            sys.stderr.write(u'E  ')
            sys.stderr.write(str(test))
            sys.stderr.write('\n')
        else:
            sys.stderr.write(u'E')

    def addFailure(self, test, err):
        '''增加截图'''
        self.failure_count += 1
        TestResult.addFailure(self, test, err)
        _, _exc_str = self.failures[-1]
        output = self.complete_output()
        self.result.append((1, test, output, _exc_str))

        if python3:
            if not getattr(test, "driver",""):
                pass
            else:
                try:
                    driver = getattr(test, "driver")
                    test.imgs.append(driver.get_screenshot_as_base64())
                except Exception as e:
                    pass
                
        if self.verbosity > 1:
            sys.stderr.write(u'F  ')
            sys.stderr.write(str(test))
            sys.stderr.write(u'\n')
        else:
            sys.stderr.write(u'F')

    def addSubTest(self, test, subtest, err):
        if err is not None:
            if getattr(self, 'failfast', False):
                self.stop()
            if issubclass(err[0], test.failureException):
                self.failure_count += 1
                errors = self.failures
                errors.append((subtest, self._exc_info_to_string(err, subtest)))
                output = self.complete_output()
                self.result.append((1, test, output + '\nSubTestCase Failed:\n' + str(subtest),
                                    self._exc_info_to_string(err, subtest)))
                if self.verbosity > 1:
                    sys.stderr.write('F  ')
                    sys.stderr.write(str(subtest))
                    sys.stderr.write('\n')
                else:
                    sys.stderr.write('F')
            else:
                self.error_count += 1
                errors = self.errors
                errors.append((subtest, self._exc_info_to_string(err, subtest)))
                output = self.complete_output()
                self.result.append(
                    (2, test, output + '\nSubTestCase Error:\n' + str(subtest), self._exc_info_to_string(err, subtest)))
                if self.verbosity > 1:
                    sys.stderr.write('E  ')
                    sys.stderr.write(str(subtest))
                    sys.stderr.write('\n')
                else:
                    sys.stderr.write('E')
            self._mirrorOutput = True
        else:
            self.subtestlist.append(subtest)
            self.subtestlist.append(test)
            self.success_count += 1
            output = self.complete_output()
            self.result.append((0, test, output + '\nSubTestCase Pass:\n' + str(subtest), ''))
            if self.verbosity > 1:
                sys.stderr.write('ok ')
                sys.stderr.write(str(subtest))
                sys.stderr.write('\n')
            else:
                sys.stderr.write('../..')

class HTMLTestReport(Template_CN):
    def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None,tester=None,images=False):
        '''增加测试者'''
        self.stream = stream
        self.verbosity = verbosity
        if title is None:
            self.title = self.DEFAULT_TITLE
        else:
            self.title = title
        if description is None:
            self.description = self.DEFAULT_DESCRIPTION
        else:
            self.description = description
        if tester is None:
            self.tester = self.DEFAULT_TESTER
        else:
            self.tester = tester

        self.IMAGES = images
        self.startTime = datetime.datetime.now()


    def run(self, test):
        "Run the given test case or test suite."
        result = _TestResult(self.verbosity)
        test(result)
        self.stopTime = datetime.datetime.now()
        self.generateReport(test, result)
        if python3:
            sys.stderr.write('\nTime Elapsed: %s\n' % (self.stopTime - self.startTime))
        else:
            print >>sys.stderr, u'\nTime Elapsed: %s' % (self.stopTime-self.startTime)
        return result


    def sortResult(self, result_list):
        # unittest does not seems to run in any particular order.
        # Here at least we want to group them together by class.
        rmap = {}
        classes = []
        for n,t,o,e in result_list:
            cls = t.__class__
            if not cls in rmap:
                rmap[cls] = []
                classes.append(cls)
            rmap[cls].append((n,t,o,e))
        r = [(cls, rmap[cls]) for cls in classes]
        return r


    def getReportAttributes(self, result):
        """
        Return report attributes as a list of (name, value).
        Override this to add custom attributes.
        """
        startTime = str(self.startTime)[:19]
        duration = str(self.stopTime - self.startTime)
        status = []
        if result.success_count: status.append(u'通过 %s'    % result.success_count)
        if result.failure_count: status.append(u'失败 %s' % result.failure_count)
        if result.error_count:   status.append(u'错误 %s'   % result.error_count)
        total = result.success_count+result.failure_count+result.error_count
        if status:
            status = ' '.join(status)
            self.passrate = str('%.1f%%' % (float(result.success_count) / total*100))
        else:
            status = 'none'
        return [
            (u'测试人员', self.tester),
            (u'开始时间', startTime),
            (u'运行时长', duration),
            (u'测试结果', '共 {}  {}  通过率:{}'.format(total, status, self.passrate))
        ]


    def generateReport(self, test, result):
        report_attrs = self.getReportAttributes(result)
        generator = u'HTMLTestRunner %s' % __version__
        stylesheet = self._generate_stylesheet()
        heading = self._generate_heading(report_attrs)
        report = self._generate_report(result)
        ending = self._generate_ending()
        chart = self._generate_chart(result)
        output = self.HTML_TMPL % dict(
            title = saxutils.escape(self.title),
            generator = generator,
            stylesheet = stylesheet,
            heading = heading,
            report = report,
            ending = ending,
            chart_script= chart

        )
        self.stream.write(output.encode('utf8'))


    def _generate_stylesheet(self):
        return self.STYLESHEET_TMPL


    def _generate_heading(self, report_attrs):
        a_lines = []
        for name, value in report_attrs:
            line = self.HEADING_ATTRIBUTE_TMPL % dict(
                    name = saxutils.escape(name),
                    value = saxutils.escape(value),
                )
            a_lines.append(line)
        heading = self.HEADING_TMPL % dict(
            title = saxutils.escape(self.title),
            parameters = ''.join(a_lines),
            description = saxutils.escape(self.description),
            tester=saxutils.escape(self.tester),
        )
        return heading


    def _generate_report(self, result):
        rows = []
        sortedResult = self.sortResult(result.result)
        for cid, (cls, cls_results) in enumerate(sortedResult):
            # subtotal for a class
            np = nf = ne = 0
            for n,t,o,e in cls_results:
                if n == 0: np += 1
                elif n == 1: nf += 1
                else: ne += 1

            # format class description
            if cls.__module__ == "__main__":
                name = cls.__name__
            else:
                name = "%s.%s" % (cls.__module__, cls.__name__)
            doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
            desc = doc and '%s: %s' % (name, doc) or name
            if self.IMAGES:
                row = self.REPORT_CLASS_TMPL_IMAGES % dict(
                    style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
                    desc = desc,
                    count = np+nf+ne,
                    Pass = np,
                    fail = nf,
                    error = ne,
                    cid = u'c%s' % (cid+1),
                )
            else:
                row = self.REPORT_CLASS_TMPL % dict(
                    style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
                    desc = desc,
                    count = np+nf+ne,
                    Pass = np,
                    fail = nf,
                    error = ne,
                    cid = u'c%s' % (cid+1),
                )
            rows.append(row)

            for tid, (n,t,o,e) in enumerate(cls_results):
                self._generate_report_test(rows, cid, tid, n, t, o, e)

        if self.IMAGES:
            report = self.REPORT_TMPL_IMAGES % dict(
                test_list = ''.join(rows),
                count = str(result.success_count+result.failure_count+result.error_count),
                Pass = str(result.success_count),
                fail = str(result.failure_count),
                error = str(result.error_count),
                passrate=self.passrate,
            )
        else:
            report = self.REPORT_TMPL % dict(
                test_list = ''.join(rows),
                count = str(result.success_count+result.failure_count+result.error_count),
                Pass = str(result.success_count),
                fail = str(result.failure_count),
                error = str(result.error_count),
                passrate=self.passrate,
            )

        return report

    def _generate_chart(self, result):
        chart = self.ECHARTS_SCRIPT % dict(
            Pass=str(result.success_count),
            fail=str(result.failure_count),
            error=str(result.error_count),
        )
        return chart

    def _generate_report_test(self, rows, cid, tid, n, t, o, e):
        # e.g. 'pt1.1', 'ft1.1', etc
        has_output = bool(o or e)
        # 增加error
        tid = (n == 0 and 'p' or n == 1 and 'f' or 'e') + 't%s_%s' % (cid + 1, tid + 1)

        name = t.id().split('.')[-1]
        doc = t.shortDescription() or ""
        desc = doc and ('%s: %s' % (name, doc)) or name
        if self.IMAGES:
            tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL_IMAGES or self.REPORT_TEST_NO_OUTPUT_TMPL_IMAGES
        else:
            tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL

        # o and e should be byte string because they are collected from stdout and stderr?
        if isinstance(o, str):
            # uo = unicode(o.encode('string_escape'))
            if python3:
                uo = o
            else:
                uo = o.decode('utf-8', 'ignore')
        else:
            uo = o
        if isinstance(e,str):
            # TODO: some problem with 'string_escape': it escape \n and mess up formating
            # ue = unicode(e.encode('string_escape'))
            if python3:
                ue = e
            else:
                # ue = e.decode('latin-1')
                ue = e.decode('utf-8', 'ignore')
        else:
            ue = e

        script = self.REPORT_TEST_OUTPUT_TMPL % dict(
            id = tid,
            output = saxutils.escape(uo+ue),
        )
        if getattr(t, 'imgs', []):
            # 判断截图列表，如果有则追加
            tmp = u""
            for i, img in enumerate(t.imgs):
                if i == 0:
                    tmp += """ <img src="data:image/jpg;base64,%s" style="display: block;" class="img"/>\n""" % img
                else:
                    tmp += """ <img src="data:image/jpg;base64,%s" style="display: none;" class="img"/>\n""" % img
            imgs = self.IMG_TMPL % dict(imgs=tmp)
        else:
            imgs = u"""没有截图"""
        row = tmpl % dict(
            tid = tid,
            Class = (n == 0 and 'hiddenRow' or 'none'),
            style = (n == 2 and 'errorCase') or (n == 1 and 'failCase') or (n == 0 and 'passCase'),
            desc = desc,
            script = script,
            status = self.STATUS[n],
            img=imgs,
        )
        rows.append(row)
        if not has_output:
            return

    def _generate_ending(self):
        return self.ENDING_TMPL

class HTMLTestReportEN(Template_EN):
    def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None,tester=None,images=False):
        '''add tester'''
        self.stream = stream
        self.verbosity = verbosity
        if title is None:
            self.title = self.DEFAULT_TITLE
        else:
            self.title = title
        if description is None:
            self.description = self.DEFAULT_DESCRIPTION
        else:
            self.description = description
        if tester is None:
            self.tester = self.DEFAULT_TESTER
        else:
            self.tester = tester

        self.IMAGES = images
        self.startTime = datetime.datetime.now()


    def run(self, test):
        "Run the given test case or test suite."
        result = _TestResult(self.verbosity)
        test(result)
        self.stopTime = datetime.datetime.now()
        self.generateReport(test, result)
        if python3:
            sys.stderr.write('\nTime Elapsed: %s\n' % (self.stopTime - self.startTime))
        else:
            print >>sys.stderr, u'\nTime Elapsed: %s' % (self.stopTime-self.startTime)
        return result


    def sortResult(self, result_list):
        # unittest does not seems to run in any particular order.
        # Here at least we want to group them together by class.
        rmap = {}
        classes = []
        for n,t,o,e in result_list:
            cls = t.__class__
            if not cls in rmap:
                rmap[cls] = []
                classes.append(cls)
            rmap[cls].append((n,t,o,e))
        r = [(cls, rmap[cls]) for cls in classes]
        return r


    def getReportAttributes(self, result):
        """
        Return report attributes as a list of (name, value).
        Override this to add custom attributes.
        """
        startTime = str(self.startTime)[:19]
        duration = str(self.stopTime - self.startTime)
        status = []
        if result.success_count: status.append(u'Pass %s'    % result.success_count)
        if result.failure_count: status.append(u'Failed %s' % result.failure_count)
        if result.error_count:   status.append(u'Error %s'   % result.error_count)
        total = result.success_count+result.failure_count+result.error_count
        if status:
            status = ' '.join(status)
            self.passrate = str('%.1f%%' % (float(result.success_count) / total*100))
        else:
            status = 'none'
        return [
            (u'Tester', self.tester),
            (u'Start Time', startTime),
            (u'Duration', duration),
            (u'Status', 'All {}  {}  Passing Rate:{}'.format(total, status, self.passrate))
        ]


    def generateReport(self, test, result):
        report_attrs = self.getReportAttributes(result)
        generator = u'HTMLTestRunner %s' % __version__
        stylesheet = self._generate_stylesheet()
        heading = self._generate_heading(report_attrs)
        report = self._generate_report(result)
        ending = self._generate_ending()
        chart = self._generate_chart(result)
        output = self.HTML_TMPL % dict(
            title = saxutils.escape(self.title),
            generator = generator,
            stylesheet = stylesheet,
            heading = heading,
            report = report,
            ending = ending,
            chart_script= chart

        )
        self.stream.write(output.encode('utf8'))


    def _generate_stylesheet(self):
        return self.STYLESHEET_TMPL


    def _generate_heading(self, report_attrs):
        a_lines = []
        for name, value in report_attrs:
            line = self.HEADING_ATTRIBUTE_TMPL % dict(
                name = saxutils.escape(name),
                value = saxutils.escape(value),
            )
            a_lines.append(line)
        heading = self.HEADING_TMPL % dict(
            title = saxutils.escape(self.title),
            parameters = ''.join(a_lines),
            description = saxutils.escape(self.description),
            tester=saxutils.escape(self.tester),
        )
        return heading


    def _generate_report(self, result):
        rows = []
        sortedResult = self.sortResult(result.result)
        for cid, (cls, cls_results) in enumerate(sortedResult):
            # subtotal for a class
            np = nf = ne = 0
            for n,t,o,e in cls_results:
                if n == 0: np += 1
                elif n == 1: nf += 1
                else: ne += 1

            # format class description
            if cls.__module__ == "__main__":
                name = cls.__name__
            else:
                name = "%s.%s" % (cls.__module__, cls.__name__)
            doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
            desc = doc and '%s: %s' % (name, doc) or name
            if self.IMAGES:
                row = self.REPORT_CLASS_TMPL_IMAGES % dict(
                    style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
                    desc = desc,
                    count = np+nf+ne,
                    Pass = np,
                    fail = nf,
                    error = ne,
                    cid = u'c%s' % (cid+1),
                )
            else:
                row = self.REPORT_CLASS_TMPL % dict(
                    style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
                    desc = desc,
                    count = np+nf+ne,
                    Pass = np,
                    fail = nf,
                    error = ne,
                    cid = u'c%s' % (cid+1),
                )
            rows.append(row)

            for tid, (n,t,o,e) in enumerate(cls_results):
                self._generate_report_test(rows, cid, tid, n, t, o, e)

        if self.IMAGES:
            report = self.REPORT_TMPL_IMAGES % dict(
                test_list = ''.join(rows),
                count = str(result.success_count+result.failure_count+result.error_count),
                Pass = str(result.success_count),
                fail = str(result.failure_count),
                error = str(result.error_count),
                passrate=self.passrate,
            )
        else:
            report = self.REPORT_TMPL % dict(
                test_list = ''.join(rows),
                count = str(result.success_count+result.failure_count+result.error_count),
                Pass = str(result.success_count),
                fail = str(result.failure_count),
                error = str(result.error_count),
                passrate=self.passrate,
            )

        return report

    def _generate_chart(self, result):
        chart = self.ECHARTS_SCRIPT % dict(
            Pass=str(result.success_count),
            fail=str(result.failure_count),
            error=str(result.error_count),
        )
        return chart

    def _generate_report_test(self, rows, cid, tid, n, t, o, e):
        # e.g. 'pt1.1', 'ft1.1', etc
        has_output = bool(o or e)
        # add error
        tid = (n == 0 and 'p' or n == 1 and 'f' or 'e') + 't%s_%s' % (cid + 1, tid + 1)

        name = t.id().split('.')[-1]
        doc = t.shortDescription() or ""
        desc = doc and ('%s: %s' % (name, doc)) or name
        if self.IMAGES:
            tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL_IMAGES or self.REPORT_TEST_NO_OUTPUT_TMPL_IMAGES
        else:
            tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL

        # o and e should be byte string because they are collected from stdout and stderr?
        if isinstance(o, str):
            # uo = unicode(o.encode('string_escape'))
            if python3:
                uo = o
            else:
                uo = o.decode('utf-8', 'ignore')
        else:
            uo = o
        if isinstance(e,str):
            # TODO: some problem with 'string_escape': it escape \n and mess up formating
            # ue = unicode(e.encode('string_escape'))
            if python3:
                ue = e
            else:
                # ue = e.decode('latin-1')
                ue = e.decode('utf-8', 'ignore')
        else:
            ue = e

        script = self.REPORT_TEST_OUTPUT_TMPL % dict(
            id = tid,
            output = saxutils.escape(uo+ue),
        )
        if getattr(t, 'imgs', []):
            # 判断截图列表，如果有则追加
            tmp = u""
            for i, img in enumerate(t.imgs):
                if i == 0:
                    tmp += """ <img src="data:image/jpg;base64,%s" style="display: block;" class="img"/>\n""" % img
                else:
                    tmp += """ <img src="data:image/jpg;base64,%s" style="display: none;" class="img"/>\n""" % img
            imgs = self.IMG_TMPL % dict(imgs=tmp)
        else:
            imgs = u"""no screenshot"""
        row = tmpl % dict(
            tid = tid,
            Class = (n == 0 and 'hiddenRow' or 'none'),
            style = (n == 2 and 'errorCase') or (n == 1 and 'failCase') or (n == 0 and 'passCase'),
            desc = desc,
            script = script,
            status = self.STATUS[n],
            img=imgs,
        )
        rows.append(row)
        if not has_output:
            return

    def _generate_ending(self):
        return self.ENDING_TMPL

##############################################################################
# Facilities for running tests from the command line
##############################################################################

# Note: Reuse unittest.TestProgram to launch test. In the future we may
# build our own launcher to support more specific command line
# parameters like test title, CSS, etc.
class TestProgram(unittest.TestProgram):
    """
    A variation of the unittest.TestProgram. Please refer to the base
    class for command line parameters.
    """
    def runTests(self):
        # Pick HTMLTestRunner as the default test runner.
        # base class's testRunner parameter is not useful because it means
        # we have to instantiate HTMLTestRunner before we know self.verbosity.
        if self.testRunner is None:
            # self.testRunner = HTMLTestRunner(verbosity=self.verbosity)
            self.testRunner = HTMLTestReport(verbosity=self.verbosity)
        unittest.TestProgram.runTests(self)

main = TestProgram

##############################################################################
# Executing this module from the command line
##############################################################################

if __name__ == "__main__":
    main(module=None)

